/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.drawee.drawable;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.VisibleForTesting;
import com.facebook.imagepipeline.systrace.FrescoSystrace;
import com.oszc.bbhmlibrary.utils.CommonUtils;
import com.oszc.bbhmlibrary.utils.LogUtil;
import ohos.agp.components.Component;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.ColorFilter;
import ohos.agp.render.Path;
import ohos.agp.utils.*;

import java.util.Arrays;

import static com.oszc.bbhmlibrary.utils.CommonUtils.printMatrix;

public abstract class RoundedDrawable extends RootShapeElement implements Rounded, TransformAwareDrawable {
    private final String TAG = RoundedDrawable.class.getSimpleName();

  private final Element mDelegate;

  /**
   * Constructs a new forwarding drawable.
   *
   * @param drawable drawable that this forwarding drawable will forward to
   */
  RoundedDrawable(PixelMapElement drawable) {
    mDelegate = drawable;
  }

  @Override
  public Element getCurrentElement() {
    return mDelegate;
  }

  protected boolean mIsCircle = false;
  protected boolean mRadiiNonZero = false;
  protected float mBorderWidth = 0;
  protected final Path mPath = new Path();
  protected boolean mIsShaderTransformDirty = true;
  protected int mBorderColor = Color.TRANSPARENT.getValue();
  protected final Path mBorderPath = new Path();

  private final float[] mCornerRadii = new float[8];
  @VisibleForTesting final float[] mBorderRadii = new float[8];
  @VisibleForTesting @Nullable
  float[] mInsideBorderRadii;

  @VisibleForTesting final RectFloat mRootBounds = new RectFloat();
  @VisibleForTesting final RectFloat mPrevRootBounds = new RectFloat();
  @VisibleForTesting final RectFloat mBitmapBounds = new RectFloat();
  @VisibleForTesting final RectFloat mDrawableBounds = new RectFloat();
  @VisibleForTesting @Nullable
  RectFloat mInsideBorderBounds;

  @VisibleForTesting final Matrix mBoundsTransform = new Matrix();
  @VisibleForTesting final Matrix mPrevBoundsTransform = new Matrix();

  @VisibleForTesting final Matrix mParentTransform = new Matrix();
  @VisibleForTesting final Matrix mPrevParentTransform = new Matrix();
  @VisibleForTesting final Matrix mInverseParentTransform = new Matrix();

  @VisibleForTesting @Nullable
  Matrix mInsideBorderTransform;
  @VisibleForTesting @Nullable
  Matrix mPrevInsideBorderTransform;

  @VisibleForTesting final Matrix mTransform = new Matrix();
  private float mPadding = 0;
  private boolean mScaleDownInsideBorders = false;
  private boolean mPaintFilterBitmap = false;

  private boolean mIsPathDirty = true;

  private @Nullable
  TransformCallback mTransformCallback;

  public Component mComponent;


  @Override
  public void setBounds(Rect bounds) {
    super.setBounds(bounds);
    mDelegate.setBounds(bounds);
  }

  @Override
  public void setBounds(int left, int top, int right, int bottom) {
    super.setBounds(left, top, right, bottom);
    mDelegate.setBounds(left,top,right,bottom);
  }

  public void setComponent(Component component){
    mComponent = component;
  }

  public void  invalidateSelf(){
      if(getCallback()!= null){
        getCallback().onChange(this);
      }
      /*
    if(mComponent != null){
      mComponent.addDrawTask(((component, canvas) -> drawToCanvas(canvas)));
    }
       */
  }

  /**
   * Sets whether to round as circle.
   *
   * @param isCircle whether or not to round as circle
   */
  @Override
  public void setCircle(boolean isCircle) {
    mIsCircle = isCircle;
    mIsPathDirty = true;
    invalidateSelf();
  }

  /** Returns whether or not this drawable rounds as circle. */
  @Override
  public boolean isCircle() {
    return mIsCircle;
  }

  /**
   * Specify radius for the corners of the rectangle. If this is > 0, then the drawable is drawn in
   * a round-rectangle, rather than a rectangle.
   *
   * @param radius the radius for the corners of the rectangle
   */
  @Override
  public void setRadius(float radius) {
    Preconditions.checkState(radius >= 0);
    Arrays.fill(mCornerRadii, radius);
    mRadiiNonZero = (radius != 0);
    mIsPathDirty = true;
    invalidateSelf();
  }

  /**
   * Specify radii for each of the 4 corners. For each corner, the array contains 2 values,
   * [X_radius, Y_radius]. The corners are ordered top-left, top-right, bottom-right, bottom-left
   *
   * @param radii the x and y radii of the corners
   */
  @Override
  public void setRadii(float[] radii) {
    if (radii == null) {
      Arrays.fill(mCornerRadii, 0);
      mRadiiNonZero = false;
    } else {
      Preconditions.checkArgument(radii.length == 8, "radii should have exactly 8 values");
      System.arraycopy(radii, 0, mCornerRadii, 0, 8);
      mRadiiNonZero = false;
      for (int i = 0; i < 8; i++) {
        mRadiiNonZero |= (radii[i] > 0);
      }
    }
    mIsPathDirty = true;
    invalidateSelf();
  }

  /** Gets the radii. */
  @Override
  public float[] getRadii() {
    return mCornerRadii;
  }

  /**
   * Sets the border
   *
   * @param color of the border
   * @param width of the border
   */
  @Override
  public void setBorder(int color, float width) {
    if (mBorderColor != color || mBorderWidth != width) {
      mBorderColor = color;
      mBorderWidth = width;
      mIsPathDirty = true;
      invalidateSelf();
    }
  }

  /** Gets the border color. */
  @Override
  public int getBorderColor() {
    return mBorderColor;
  }

  /** Gets the border width. */
  @Override
  public float getBorderWidth() {
    return mBorderWidth;
  }

  /**
   * Sets the padding for the bitmap.
   *
   * @param padding
   */
  @Override
  public void setPadding(float padding) {
    if (mPadding != padding) {
      mPadding = padding;
      mIsPathDirty = true;
      invalidateSelf();
    }
  }

  /** Gets the padding. */
  @Override
  public float getPadding() {
    return mPadding;
  }

  /**
   * Sets whether image should be scaled down inside borders.
   *
   * @param scaleDownInsideBorders
   */
  @Override
  public void setScaleDownInsideBorders(boolean scaleDownInsideBorders) {
    LogUtil.error(TAG,"setScaleDownInsideBorders A "+ scaleDownInsideBorders);
    if (mScaleDownInsideBorders != scaleDownInsideBorders) {
      LogUtil.error(TAG,"setScaleDownInsideBorders B");
      mScaleDownInsideBorders = scaleDownInsideBorders;
      mIsPathDirty = true;
      invalidateSelf();
    }
  }

  /** Gets whether image should be scaled down inside borders. */
  @Override
  public boolean getScaleDownInsideBorders() {
    return mScaleDownInsideBorders;
  }

  /**
   * Sets FILTER_BITMAP_FLAG flag to Paint. {@link ohos.agp.render.Paint}
   *
   * <p>This should generally be on when drawing bitmaps, unless performance-bound (rendering to
   * software canvas) or preferring pixelation artifacts to blurriness when scaling significantly.
   *
   * @param paintFilterBitmap whether to set FILTER_BITMAP_FLAG flag to Paint.
   */
  @Override
  public void setPaintFilterBitmap(boolean paintFilterBitmap) {
    if (mPaintFilterBitmap != paintFilterBitmap) {
      mPaintFilterBitmap = paintFilterBitmap;
      invalidateSelf();
    }
  }


  /** Gets whether to set FILTER_BITMAP_FLAG flag to Paint. */
  @Override
  public boolean getPaintFilterBitmap() {
    return mPaintFilterBitmap;
  }

  /** TransformAwareDrawable method */
  @Override
  public void setTransformCallback(@Nullable TransformCallback transformCallback) {
    mTransformCallback = transformCallback;
  }

  protected void updateTransform() {
    LogUtil.error(TAG,"updateTransform A");
    if (mTransformCallback != null) {
      LogUtil.error(TAG,"updateTransform B");
      mTransformCallback.getTransform(mParentTransform);
      mTransformCallback.getRootBounds(mRootBounds);
    } else {
      LogUtil.error(TAG,"updateTransform C");
      mParentTransform.reset();
      mRootBounds.left = getBounds().left;
      mRootBounds.right = getBounds().right;
      mRootBounds.top = getBounds().top;
      mRootBounds.bottom = getBounds().bottom;
    }
    LogUtil.error(TAG,"updateTransform D");

//    mBitmapBounds.set(0, 0, getIntrinsicWidth(), getIntrinsicHeight());
    mBitmapBounds.left = 0;
    mBitmapBounds.top = 0;
    mBitmapBounds.right = getIntrinsicWidth();
    mBitmapBounds.bottom = getIntrinsicHeight();
//    mDrawableBounds.set(mDelegate.getBounds());
    mDrawableBounds.left = mDelegate.getBounds().left;
    mDrawableBounds.top = mDelegate.getBounds().top;
    mDrawableBounds.right = mDelegate.getBounds().right;
    mDrawableBounds.bottom = mDelegate.getBounds().bottom;

    mBoundsTransform.setRectToRect(mBitmapBounds, mDrawableBounds, Matrix.ScaleToFit.FILL);
    if (mScaleDownInsideBorders) {
        LogUtil.error(TAG,"updateTransform E");
      if (mInsideBorderBounds == null) {
        LogUtil.error(TAG,"updateTransform F");
        mInsideBorderBounds = new RectFloat(mRootBounds);
      } else {
        LogUtil.error(TAG,"updateTransform G");
//        mInsideBorderBounds.set(mRootBounds);
        mInsideBorderBounds.left = mRootBounds.left;
        mInsideBorderBounds.top = mRootBounds.top;
        mInsideBorderBounds.right = mRootBounds.right;
        mInsideBorderBounds.bottom = mRootBounds.bottom;
      }
      LogUtil.error(TAG,"updateTransform H");
//      mInsideBorderBounds.inset(mBorderWidth, mBorderWidth);
      mInsideBorderBounds.left +=  mBorderWidth;
      mInsideBorderBounds.top += mBorderWidth;
      mInsideBorderBounds.right -= mBorderWidth;
      mInsideBorderBounds.bottom -= mBorderWidth;

      if (mInsideBorderTransform == null) {
        LogUtil.error(TAG,"updateTransform I");
        mInsideBorderTransform = new Matrix();
      }
      mInsideBorderTransform.setRectToRect(mRootBounds, mInsideBorderBounds, Matrix.ScaleToFit.FILL);
      LogUtil.error(TAG,"-------updateTransform mInsideBorderTransform START----------");
      CommonUtils.printMatrix(mInsideBorderTransform);
      LogUtil.error(TAG,"-------updateTransform mInsideBorderTransform START----------");

    } else if (mInsideBorderTransform != null) {
      LogUtil.error(TAG,"updateTransform J");
      mInsideBorderTransform.reset();
    }
    LogUtil.error(TAG,"updateTransform K");
    if (!mParentTransform.equals(mPrevParentTransform)
        || !mBoundsTransform.equals(mPrevBoundsTransform)
        || (mInsideBorderTransform != null
            && !mInsideBorderTransform.equals(mPrevInsideBorderTransform))) {
      LogUtil.error(TAG,"updateTransform L");
      mIsShaderTransformDirty = true;

      mParentTransform.invert(mInverseParentTransform);
      mTransform.setMatrix(mParentTransform);
      if (mScaleDownInsideBorders) {
        LogUtil.error(TAG,"updateTransform M");
        mTransform.postConcat(mInsideBorderTransform);
      }
      LogUtil.error(TAG,"updateTransform N");
      mTransform.preConcat(mBoundsTransform);

      mPrevParentTransform.setMatrix(mParentTransform);
      mPrevBoundsTransform.setMatrix(mBoundsTransform);
      if (mScaleDownInsideBorders) {
        if (mPrevInsideBorderTransform == null) {
          mPrevInsideBorderTransform = new Matrix(mInsideBorderTransform);
        } else {
          mPrevInsideBorderTransform.setMatrix(mInsideBorderTransform);
        }
      } else if (mPrevInsideBorderTransform != null) {
        mPrevInsideBorderTransform.reset();
      }
    }

    if (!mRootBounds.equals(mPrevRootBounds)) {
      mIsPathDirty = true;
//      mPrevRootBounds.set(mRootBounds);
      mPrevRootBounds.left = mRootBounds.left;
      mPrevRootBounds.right = mRootBounds.right;
      mPrevRootBounds.top = mRootBounds.top;
      mPrevRootBounds.bottom = mRootBounds.bottom;
    }
    LogUtil.error(TAG, "mScaleDownInsideBorders:"+mScaleDownInsideBorders
            + "   updateTransform: mRootBounds:" + mRootBounds
            + "   mBitmapBounds:" + mBitmapBounds
            + "   mDelegateBounds:" + mDelegate.getBounds()
            + "   mPrevRootBounds:" + mPrevRootBounds);

    LogUtil.error(TAG,"-------updateTransform mTransform START----------");
    printMatrix(mTransform);
    LogUtil.error(TAG,"-------updateTransform mTransform END----------");
  }

  protected void updatePath() {
    LogUtil.error(TAG,"updatePath A mBorderWidth:"+mBorderWidth+"   mScaleDownInsideBorders:"+mScaleDownInsideBorders);
    if (mIsPathDirty) {
        LogUtil.error(TAG,"updatePath B");
      mBorderPath.reset();
      mRootBounds.top += mBorderWidth / 2;
      mRootBounds.left += mBorderWidth / 2;
      mRootBounds.right -= mBorderWidth / 2;
      mRootBounds.bottom -= mBorderWidth / 2;
      if (mIsCircle) {
        LogUtil.error(TAG,"updatePath C");
        float radius = Math.min(mRootBounds.getWidth(), mRootBounds.getHeight()) / 2;
        mBorderPath.addCircle(new Point((mRootBounds.left + mRootBounds.right) * 0.5f,(mRootBounds.top + mRootBounds.bottom) * 0.5f),radius,Path.Direction.CLOCK_WISE);
      } else {
        LogUtil.error(TAG,"updatePath D");
        for (int i = 0; i < mBorderRadii.length; i++) {
          mBorderRadii[i] = mCornerRadii[i] + mPadding - mBorderWidth / 2;
        }
        mBorderPath.addRoundRect(mRootBounds, mBorderRadii, Path.Direction.CLOCK_WISE);
      }
      mRootBounds.top += -mBorderWidth / 2;
      mRootBounds.left += -mBorderWidth / 2;
      mRootBounds.right -= -mBorderWidth / 2;
      mRootBounds.bottom -= -mBorderWidth / 2;
      mPath.reset();
      float totalPadding = mPadding + (mScaleDownInsideBorders ? mBorderWidth : 0);
      mRootBounds.top +=   totalPadding;
      mRootBounds.left +=  totalPadding;
      mRootBounds.right -= totalPadding;
      mRootBounds.bottom -= totalPadding;
      if (mIsCircle) {
        Point centerPoint = new Point((mRootBounds.left + mRootBounds.right) * 0.5f,(mRootBounds.top + mRootBounds.bottom) * 0.5f);
        float radius = Math.min(mRootBounds.getWidth(), mRootBounds.getHeight()) / 2;
        LogUtil.error(TAG,"updatePath E centerPoint:"+centerPoint +"   radius:"+radius);
        mPath.addCircle(centerPoint,radius,Path.Direction.CLOCK_WISE);
      } else if (mScaleDownInsideBorders) {
        if (mInsideBorderRadii == null) {
          mInsideBorderRadii = new float[8];
        }
        for (int i = 0; i < mBorderRadii.length; i++) {
          mInsideBorderRadii[i] = mCornerRadii[i] - mBorderWidth;
        }
        LogUtil.error(TAG,"updatePath F  mRootBounds:"+mRootBounds +"   mInsideBorderRadii:"+mInsideBorderRadii);
        mPath.addRoundRect(mRootBounds, mInsideBorderRadii, Path.Direction.CLOCK_WISE);
      } else {
        LogUtil.error(TAG,"updatePath G  mRootBounds:"+mRootBounds +"   mCornerRadii:"+mCornerRadii);
        mPath.addRoundRect(mRootBounds, mCornerRadii, Path.Direction.CLOCK_WISE);
      }
      mRootBounds.top += -(totalPadding);
      mRootBounds.left += -(totalPadding);
      mRootBounds.right -= -(totalPadding);
      mRootBounds.bottom -= -(totalPadding);
      mPath.setFillType(Path.FillType.WINDING_ORDER);
      mIsPathDirty = false;
    }
  }

  /** If both the radii and border width are zero, there is nothing to round. */
  @VisibleForTesting
  boolean shouldRound() {
    return (mIsCircle || mRadiiNonZero || mBorderWidth > 0);
  }


  @Override
  public int getWidth() {
    return mDelegate.getWidth();
  }

  @Override
  public int getIntrinsicWidth() {
      LogUtil.error(TAG,"getIntrinsicWidth delegate class name: "+ mDelegate.getClass().getSimpleName());
    if(mDelegate instanceof RootShapeElement){
      int width = ((RootShapeElement) mDelegate).getIntrinsicWidth();
      LogUtil.error(TAG,"getIntrinsicWidth B width:"+width);
      return width;
    }
    return super.getIntrinsicWidth();
  }

  @Override
  public int getHeight() {
    return mDelegate.getHeight();
  }

  @Override
  public int getIntrinsicHeight() {
    if(mDelegate instanceof RootShapeElement){
      return ((RootShapeElement) mDelegate).getIntrinsicHeight();
    }
    return super.getIntrinsicHeight();
  }

  //  @Override
//  public int getOpacity() {
//    return mDelegate.getOpacity();
//  }

//  @Override
//  public void setColorFilter(int color, @NonNull PorterDuff.Mode mode) {
//    mDelegate.setColorFilter(color, mode);
//  }
//
  @Override
  public void setColorFilter(@Nullable ColorFilter colorFilter) {
      if(mDelegate instanceof RootShapeElement){
        ((RootShapeElement) mDelegate).setColorFilter(colorFilter);
      }
  }

//  @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
//  @Nullable
//  @Override
//  public ColorFilter getColorFilter() {
//    return mDelegate.getColorFilter();
//  }
//
//  @Override
//  public void clearColorFilter() {
//    mDelegate.clearColorFilter();
//  }

//  @RequiresApi(api = Build.VERSION_CODES.KITKAT)
  @Override
  public int getAlpha() {
    return mDelegate.getAlpha();
  }

  @Override
  public void setAlpha(int alpha) {
    mDelegate.setAlpha(alpha);
  }

  @Override
  public void drawToCanvas(@NonNull Canvas canvas) {
    LogUtil.error(TAG,"drawToCanvas ~~~");
    if (FrescoSystrace.isTracing()) {
      FrescoSystrace.beginSection("RoundedDrawable#draw");
    }
    mDelegate.drawToCanvas(canvas);
    if (FrescoSystrace.isTracing()) {
      FrescoSystrace.endSection();
    }
  }
}
